#ifndef windows_x86
#include <core_debug.h>
#endif
#include "softplc.h"
#include <string.h>
#ifdef RTE_APP
#include "plc_abi.h"
#include "plc_config.h"
#endif
// TIME __CURRENT_TIME;
unsigned int retain_size = 0;

extern "C"
{
    void *UnpackVar(const __Unpack_desc_type *dsc, void **real_value_p, char *flags);
    typedef void (*__for_each_variable_do_fp)(const dbgvardsc_t *);
    void __for_each_variable_do(__for_each_variable_do_fp fp);
    /* GetRetainSizeIterator */
    void GetRetainSizeIterator(const dbgvardsc_t *dsc)
    {
        void *real_value_p = NULL;
        char flags = 0;
        UnpackVar(dsc, &real_value_p, &flags);

        if (flags & __IEC_RETAIN_FLAG)
        {
            USINT size = __get_type_enum_size(dsc->type);
            /* Calc retain buffer size */
            retain_size += size;
        }
    }

    /* Return size of all retain variables */
    unsigned int GetRetainSize(void)
    {
        __for_each_variable_do(GetRetainSizeIterator);
        return retain_size;
    }

    unsigned char __get_type_enum_size(__IEC_types_enum t)
    {
        switch (t)
        {
            __ANY(__decl_size_case)
        /* size do not correspond to real struct.
   * only a bool is used to represent state*/
        default:
            return 0;
        }
        return 0;
    }
    void *UnpackVar(const __Unpack_desc_type *dsc, void **real_value_p, char *flags)
    {
        void *varp = dsc->ptr;
        void *forced_value_p = NULL;
        *flags = 0;
        /* find data to copy*/
        switch (dsc->type)
        {
            __ANY(__Unpack_case_t)
            __ANY(__Unpack_case_p)
        default:
            break;
        }
        if (*flags & __IEC_FORCE_FLAG)
            return forced_value_p;
        return *real_value_p;
    }
    extern unsigned int sizeof_dbgvardsc;
    static unsigned int retain_offset = 0;
#ifdef PLC
    typedef void (*__for_each_variable_do_fp)(const dbgvardsc_t *);
    void __for_each_variable_do(__for_each_variable_do_fp fp)
    {
        unsigned int i;
        for (i = 0; i < sizeof_dbgvardsc / sizeof(dbgvardsc_t); i++)
        {
            const dbgvardsc_t *dsc = &dbgvardsc[i];
            if (dsc->type != UNKNOWN_ENUM)
                (*fp)(dsc);
        }
    }

    void RemindIterator(const dbgvardsc_t *dsc)
    {
        void *real_value_p = NULL;
        char flags = 0;
        UnpackVar(dsc, &real_value_p, &flags);

        if (flags & __IEC_RETAIN_FLAG)
        {
            USINT size = __get_type_enum_size(dsc->type);
            /* compute next cursor positon*/
            unsigned int next_retain_offset = retain_offset + size;
            /* if buffer not full */
            Remind(retain_offset, size, real_value_p);
            RetainCmp(retain_offset, size, real_value_p);
            /* increment cursor according size*/
            retain_offset = next_retain_offset;
        }
    }
#endif
#ifdef PLC
    void __init_debug(void)
    {
        /* init local static vars */
#ifdef ONLINE_DEBUG
        buffer_cursor = debug_buffer;
        buffer_state = BUFFER_FREE;
#endif

        retain_offset = 0;
        InitRetain();
        /* Iterate over all variables to fill debug buffer */
        if (CheckRetainBuffer())
        {
            __for_each_variable_do(RemindIterator);
        }
        else
        {
            // char mstr[] = "RETAIN memory invalid - defaults used";
            // LogMessage(LOG_WARNING, mstr, sizeof(mstr));
        }
        retain_offset = 0;
        RetainBufferClose();
#ifdef ONLINE_DEBUG
        ResetDebugVariables();
#endif
        startRetainThd();
    }

    void __cleanup_debug(void)
    {
#ifdef ONLINE_DEBUG
        buffer_cursor = debug_buffer;
        InitiateDebugTransfer();
#endif

        CleanupRetain();
    }

    void __retrieve_debug(void)
    {
    }

    static inline void BufferIterator(const dbgvardsc_t *dsc, int do_debug)
    {
        void *real_value_p = NULL;
        void *visible_value_p = NULL;
        char flags = 0;

        visible_value_p = UnpackVar(dsc, &real_value_p, &flags);

        if (flags & (__IEC_DEBUG_FLAG | __IEC_RETAIN_FLAG))
        {
            USINT size = __get_type_enum_size(dsc->type);

#ifdef ONLINE_DEBUG
            if (flags & __IEC_DEBUG_FLAG)
            {
                /* copy visible variable to buffer */;
                if (do_debug)
                {
                    /* compute next cursor positon.
                   No need to check overflow, as BUFFER_SIZE
                   is computed large enough */
                    if ((dsc->type == STRING_ENUM) ||
                        (dsc->type == STRING_P_ENUM) ||
                        (dsc->type == STRING_O_ENUM))
                    {
                        /* optimization for strings */
                        size = ((STRING *)visible_value_p)->len + 1;
                    }
                    char *next_cursor = buffer_cursor + size;
                    /* copy data to the buffer */
                    memcpy(buffer_cursor, visible_value_p, size);
                    /* increment cursor according size*/
                    buffer_cursor = next_cursor;
                }
                /* re-force real value of outputs (M and Q)*/
                if ((flags & __IEC_FORCE_FLAG) && (flags & __IEC_OUTPUT_FLAG))
                {
                    memcpy(real_value_p, visible_value_p, size);
                }
            }
#endif

            if (flags & __IEC_RETAIN_FLAG)
            {
                /* compute next cursor positon*/
                unsigned int next_retain_offset = retain_offset + size;
                /* if buffer not full */
                Retain(retain_offset, size, real_value_p);
                /* increment cursor according size*/
                retain_offset = next_retain_offset;
            }
        }
    }

    void DebugIterator(const dbgvardsc_t *dsc)
    {
        BufferIterator(dsc, 1);
    }

    void RetainIterator(const dbgvardsc_t *dsc)
    {
        BufferIterator(dsc, 0);
    }

#ifdef ONLINE_DEBUG
#define __RegisterDebugVariable_case_t(TYPENAME)                          \
    case TYPENAME##_ENUM:                                                 \
        ((__IEC_##TYPENAME##_t *)varp)->flags |= flags;                   \
        if (force)                                                        \
            ((__IEC_##TYPENAME##_t *)varp)->value = *((TYPENAME *)force); \
        break;
#define __RegisterDebugVariable_case_p(TYPENAME)                             \
    case TYPENAME##_P_ENUM:                                                  \
        ((__IEC_##TYPENAME##_p *)varp)->flags |= flags;                      \
        if (force)                                                           \
            ((__IEC_##TYPENAME##_p *)varp)->fvalue = *((TYPENAME *)force);   \
        break;                                                               \
    case TYPENAME##_O_ENUM:                                                  \
        ((__IEC_##TYPENAME##_p *)varp)->flags |= flags;                      \
        if (force)                                                           \
        {                                                                    \
            ((__IEC_##TYPENAME##_p *)varp)->fvalue = *((TYPENAME *)force);   \
            *(((__IEC_##TYPENAME##_p *)varp)->value) = *((TYPENAME *)force); \
        }                                                                    \
        break;

    void RegisterDebugVariable(unsigned int idx, void *force)
    {
        if (idx < sizeof_dbgvardsc / sizeof(dbgvardsc_t))
        {
            unsigned char flags = force ? __IEC_DEBUG_FLAG | __IEC_FORCE_FLAG : __IEC_DEBUG_FLAG;
            const dbgvardsc_t *dsc = &dbgvardsc[idx];
            void *varp = dsc->ptr;
            switch (dsc->type)
            {
                __ANY(__RegisterDebugVariable_case_t)
                __ANY(__RegisterDebugVariable_case_p)
            default:
                break;
            }
        }
    }
#endif
#define __ResetDebugVariablesIterator_case_t(TYPENAME)                                   \
    case TYPENAME##_ENUM:                                                                \
        ((__IEC_##TYPENAME##_t *)varp)->flags &= ~(__IEC_DEBUG_FLAG | __IEC_FORCE_FLAG); \
        break;

#define __ResetDebugVariablesIterator_case_p(TYPENAME)                                   \
    case TYPENAME##_P_ENUM:                                                              \
    case TYPENAME##_O_ENUM:                                                              \
        ((__IEC_##TYPENAME##_p *)varp)->flags &= ~(__IEC_DEBUG_FLAG | __IEC_FORCE_FLAG); \
        break;

    void ResetDebugVariablesIterator(const dbgvardsc_t *dsc)
    {
        /* force debug flag to 0*/
        void *varp = dsc->ptr;
        switch (dsc->type)
        {
            __ANY(__ResetDebugVariablesIterator_case_t)
            __ANY(__ResetDebugVariablesIterator_case_p)
        default:
            break;
        }
    }

    void ResetDebugVariables(void)
    {
        __for_each_variable_do(ResetDebugVariablesIterator);
    }

    void FreeDebugData(void)
    {
        /* atomically mark buffer as free */
        AtomicCompareExchange(
            &buffer_state,
            BUFFER_BUSY,
            BUFFER_FREE);
    }

    /* Wait until debug data ready and return pointer to it */
    int GetDebugData(unsigned long *tick, unsigned long *size, void **buffer)
    {
        int wait_error = WaitDebugData(tick);
        if (!wait_error)
        {
            *size = buffer_cursor - debug_buffer;
            *buffer = debug_buffer;
        }
        return wait_error;
    }

    void __publish_debug(void)
    {
        retain_offset = 0;

#ifdef ONLINE_DEBUG
        /* Check there is no running debugger re-configuration */
        if (TryEnterDebugSection())
        {
            /* Lock buffer */
            long latest_state = AtomicCompareExchange(
                &buffer_state,
                BUFFER_FREE,
                BUFFER_BUSY);

            /* If buffer was free */
            if (latest_state == BUFFER_FREE)
            {
                /* Reset buffer cursor */
                buffer_cursor = debug_buffer;
                /* Iterate over all variables to fill debug buffer */
                __for_each_variable_do(DebugIterator);

                /* Leave debug section,
             * Trigger asynchronous transmission
             * (returns immediately) */
                InitiateDebugTransfer(); /* size */
            }
            else
            {
                /* when not debugging, do only retain */
                __for_each_variable_do(RetainIterator);
            }
            LeaveDebugSection();
        }
        else
#endif
        {
            /* when not debugging, do only retain */
            __for_each_variable_do(RetainIterator);
        }
    }
#endif
}
#ifdef PLC
#ifdef ONLINE_DEBUG
#ifndef windows_x86

#include "ArduinoJson.h"
#include "msgpack.hpp"

#endif
#endif
#endif